package com.logicfishsoftware.owlet.sesame.tests;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

import junit.framework.TestCase;

import org.openrdf.model.Graph;
import org.openrdf.model.Resource;
import org.openrdf.model.impl.GraphImpl;
import org.openrdf.model.util.GraphUtil;
import org.openrdf.model.util.GraphUtilException;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.config.RepositoryConfig;
import org.openrdf.repository.config.RepositoryConfigException;
import org.openrdf.repository.config.RepositoryConfigSchema;
import org.openrdf.repository.manager.RepositoryManager;
import org.openrdf.repository.manager.RepositoryProvider;
import org.openrdf.repository.object.ObjectConnection;
import org.openrdf.repository.object.ObjectRepository;
import org.openrdf.repository.object.config.ObjectRepositoryConfig;
import org.openrdf.repository.object.config.ObjectRepositoryFactory;
import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.rio.RDFParseException;
import org.openrdf.rio.RDFParser;
import org.openrdf.rio.Rio;
import org.openrdf.rio.helpers.StatementCollector;

import com.logicfishsoftware.owlet.app.ApplicationException;
import com.logicfishsoftware.owlet.app.Operator;
import com.logicfishsoftware.owlet.app.object.ObjectException;
import com.logicfishsoftware.owlet.ext.sesame.AlibabaApplication;

/**
 * Unit test for simple AlibabaApplication.
 */
public abstract class AlibabaApplicationTests extends TestCase {

	public abstract class AlibabaTestOperator implements AlibabaApplication.AlibabaOperator {
		public ObjectConnection getConnection()
				throws ApplicationException {
			try {
				if (AlibabaApplicationTests.this.objectConnection == null)
					objectConnection = createObjectConnection(true, repositoryTemplate);
				else if(!AlibabaApplicationTests.this.objectConnection.isOpen())
					objectConnection = createObjectConnection(true, repositoryTemplate);
			} catch (RepositoryConfigException e) {
				throw new ObjectException(e);
			} catch (RepositoryException e) {
				throw new ObjectException(e);
			} catch (RDFParseException e) {
				throw new ObjectException(e);
			} catch (RDFHandlerException e) {
				throw new ObjectException(e);
			} catch (GraphUtilException e) {
				throw new ObjectException(e);
			} catch (IOException e) {
				throw new ObjectException(e);
			}
			return AlibabaApplicationTests.this.objectConnection;
		}

		public void dispose(ObjectConnection connection)
				throws ObjectException {
			try {
				connection.close();
			} catch (RepositoryException e) {
				throw new ObjectException(e);
			}
		}

		/* (non-Javadoc)
		 * @see com.logicfishsoftware.owlet.app.Operator#proceed()
		 */
		public Operator<AlibabaApplication.AlibabaContext> proceed()
				throws ApplicationException {
			try {
				assertFalse(objectConnection.isOpen());
			} catch (RepositoryException e) {
				throw new ObjectException(e);
			}
			return null;
		}
		
	}

	protected String repositoryTemplate;
	protected AlibabaApplication application;
	protected ObjectConnection objectConnection;
	private RepositoryManager repositoryManager;
	private ObjectRepository objectRepository;

	/**
	 * Create the test case
	 * 
	 * @param testName
	 *            name of the test case
	 */
	public AlibabaApplicationTests(String testName,String repositoryTemplate) {
		super(testName);
		this.repositoryTemplate = repositoryTemplate;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see junit.framework.TestCase#setUp()
	 */
	@Override
	protected void setUp() throws Exception {
		application = new AlibabaApplication();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see junit.framework.TestCase#tearDown()
	 */
	@Override
	protected void tearDown() throws Exception {
		if (this.objectConnection != null && this.objectConnection.isOpen())
			objectConnection.close();
	}

	/**
	 * @param compile
	 * @param template
	 * @return
	 * @throws RepositoryException
	 * @throws RepositoryConfigException
	 * @throws IOException
	 * @throws GraphUtilException
	 * @throws RDFHandlerException
	 * @throws RDFParseException
	 */
	private ObjectConnection createObjectConnection(boolean compile,
			String template) throws RepositoryConfigException,
			RepositoryException, RDFParseException, RDFHandlerException,
			GraphUtilException, IOException {
		if (objectRepository == null) {
			this.repositoryManager = RepositoryProvider
					.getRepositoryManager("target");
			assertNotNull(this.repositoryManager);
			URL url = getClass().getClassLoader().getResource(template);
			assertNotNull(url);
			repositoryManager.addRepositoryConfig(createConfig(url));
			Repository repository = repositoryManager.getRepository("owlet");
			if (repository == null)
				throw new IllegalArgumentException(
						"Repository id and config id don't match: " + "owlet");
			ObjectRepositoryFactory factory = new ObjectRepositoryFactory();
			ObjectRepositoryConfig orConfig = factory.getConfig();
			orConfig.setCompileRepository(compile);
			objectRepository = factory.createRepository(orConfig, repository);
		}

		ObjectConnection connection = objectRepository.getConnection();
		connection.setAutoCommit(false);
		return connection;
	}

	private static RepositoryConfig createConfig(URL url) throws IOException,
			RDFParseException, RDFHandlerException, GraphUtilException,
			RepositoryConfigException {
		Graph graph = new GraphImpl();
		RDFParser rdfParser = Rio.createParser(RDFFormat.TURTLE);
		rdfParser.setRDFHandler(new StatementCollector(graph));

		String base = new File(".").getAbsoluteFile().toURI().toASCIIString();
		URLConnection con = url.openConnection();
		StringBuilder sb = new StringBuilder();
		for (String mimeType : RDFFormat.TURTLE.getMIMETypes()) {
			if (sb.length() < 1) {
				sb.append(", ");
			}
			sb.append(mimeType);
		}
		con.setRequestProperty("Accept", sb.toString());
		InputStream in = con.getInputStream();
		try {
			rdfParser.parse(in, base);
		} finally {
			in.close();
		}

		Resource node = GraphUtil.getUniqueSubject(graph, RDF.TYPE,
				RepositoryConfigSchema.REPOSITORY);
		RepositoryConfig config = RepositoryConfig.create(graph, node);
		config.validate();
		return config;
	}

	/**
	 * Rigourous Test :-)
	 * 
	 * @throws ApplicationException
	 * @throws RepositoryException 
	 */
	public void testCommit() throws ApplicationException, RepositoryException {
		final String iri = "http://example.org/test/test-object";

		application.enter(new AlibabaTestOperator() {
			public boolean application(AlibabaApplication.AlibabaContext context) throws Exception {
				AlibabaTestObject obj = new AlibabaTestObjectDefault();
				context.put(obj, iri);
				return true;
			}
		});

		application.enter(new AlibabaTestOperator() {
			public boolean application(AlibabaApplication.AlibabaContext context) throws Exception {
				Object obj;
				obj = context.get(iri, AlibabaTestObject.class);
				assertNotNull(obj);
				assertTrue(obj instanceof AlibabaTestObject);
				return true;
			}
		});

		application.enter(new AlibabaTestOperator() {
			public boolean application(AlibabaApplication.AlibabaContext context) throws Exception {
				context.clear();
				return true;
			}
		});

		application.enter(new AlibabaTestOperator() {
			public boolean application(AlibabaApplication.AlibabaContext context) throws Exception {
				Object obj = context.get(iri, AlibabaTestObject.class);
				// assertNull(obj); //
				assertFalse(obj instanceof AlibabaTestObject);
				return true;
			}
		});

	}

	public void testRollback() throws ApplicationException, RepositoryException {
		final String iri = "http://example.org/test/test-object-rollback";

		application.enter(new AlibabaTestOperator() {
			public boolean application(AlibabaApplication.AlibabaContext context) throws Exception {
				AlibabaTestObject obj = new AlibabaTestObjectDefault();
				context.put(obj, iri);
				return false;
			}
		});

		application.enter(new AlibabaTestOperator() {
			public boolean application(AlibabaApplication.AlibabaContext context) throws Exception {
				Object obj = context.get(iri, AlibabaTestObject.class);
				// assertNull(obj);
				assertFalse(obj instanceof AlibabaTestObject);
				return true;
			}
		});
	}

	public void testRollbackOnException() throws ApplicationException, RepositoryException {
		final String iri = "http://example.org/test/test-object-rollback";

		try {
			application.enter(new AlibabaTestOperator() {
				public boolean application(AlibabaApplication.AlibabaContext context)
						throws Exception {
					AlibabaTestObject obj = new AlibabaTestObjectDefault();
					context.put(obj, iri);
					throw new Exception();
				}
			});
		} catch (ApplicationException e) {
		}

		application.enter(new AlibabaTestOperator() {
			public boolean application(AlibabaApplication.AlibabaContext context) throws Exception {
				Object obj = context.get(iri, AlibabaTestObject.class);
				// assertNull(obj);
				assertFalse(obj instanceof AlibabaTestObject);
				return true;
			}
		});

	}

}
